package freespiritsocial.io.user.ui.fragment.message

import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import android.os.Bundle
import android.view.Gravity
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.luck.picture.lib.entity.LocalMedia
import com.sendbird.android.*
import com.sendbird.android.BaseChannel.SendFileMessageHandler
import com.sendbird.android.BaseChannel.SendUserMessageHandler
import com.sendbird.android.FileMessage.ThumbnailSize
import com.sendbird.android.SendBird.UserBlockHandler
import freespiritsocial.io.user.R
import freespiritsocial.io.user.base.BaseFragment
import freespiritsocial.io.user.chat.ChannelManager
import freespiritsocial.io.user.chat.ConnectionManager
import freespiritsocial.io.user.data.UserViewInfo
import freespiritsocial.io.user.data.model.BtnItems
import freespiritsocial.io.user.data.model.bean.UserInfo
import freespiritsocial.io.user.databinding.FrgChatBinding
import freespiritsocial.io.user.ext.hideSoftKeyboard
import freespiritsocial.io.user.ext.init
import freespiritsocial.io.user.ext.setNbOnItemClickListener
import freespiritsocial.io.user.ui.adapter.ChatAdapter
import freespiritsocial.io.user.ui.fragment.post.PostDetailFrg
import freespiritsocial.io.user.util.CacheUtil
import freespiritsocial.io.user.util.PreViewUtils
import freespiritsocial.io.user.viewmodel.ChatViewModel
import freespiritsocial.io.user.weight.BottomSheetListDialog
import freespiritsocial.io.user.weight.PictureDialog
import kotlinx.android.synthetic.main.item_add_media.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.hgj.jetpackmvvm.base.appContext
import me.hgj.jetpackmvvm.ext.nav
import me.hgj.jetpackmvvm.ext.navigateAction
import me.hgj.jetpackmvvm.ext.util.dp2px
import me.hgj.jetpackmvvm.ext.view.isEmpty
import java.io.File
import java.io.FileOutputStream
import java.util.*

class ChatFrag: BaseFragment<ChatViewModel,FrgChatBinding>() {
    companion object {
        const val EXTRA_GROUP_CHANNEL_URL = "GROUP_CHANNEL_URL"
        const val EXTRA_GROUP_CHANNEL_NAME = "GROUP_CHANNEL_NAME"
        const val CHANNEL_LIST_LIMIT = 30
    }

    private val accountIsDel get() = mDatabind.tvNickname.isEmpty()

    private val headerView by lazy(LazyThreadSafetyMode.NONE) {
        TextView(context).apply {
            textSize = 11f
            setTextColor(0xff6c778b.toInt())
            gravity = Gravity.CENTER
            layoutParams = ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT)
                .apply { setMargins(0,dp2px(20),0,dp2px(20)) }
        }
    }

    private val preViewUtils
        get() = PreViewUtils(
            requireActivity(),
            chatAdapter.data.filter { it is FileMessage && it.sendingStatus == BaseMessage.SendingStatus.SUCCEEDED }
                .mapIndexedTo(arrayListOf<UserViewInfo>(),{ index,message ->
                    val fileMessage = message as FileMessage
                    val isImage = message.type.toLowerCase(Locale.getDefault()).startsWith("image")
                    val url = if (fileMessage.data.isNullOrEmpty()) fileMessage.url else fileMessage.data
                    UserViewInfo(if (isImage) "" else fileMessage.url,url,index)
                })
        )

    val mine: UserInfo? by lazy(LazyThreadSafetyMode.NONE) {
        CacheUtil.getUser()
    }
    private val channelUrl by lazy(LazyThreadSafetyMode.NONE) {
        arguments?.getString(EXTRA_GROUP_CHANNEL_URL) ?: ""
    }
    private val chatAdapter: ChatAdapter by lazy(LazyThreadSafetyMode.NONE) {
        ChatAdapter(eventViewModel).apply {
            urlClick = {
                mViewModel.getPost(it.toLongOrNull()) {
                    nav().navigateAction(
                        R.id.postDetailFrg,
                        bundle = bundleOf(PostDetailFrg.KEY_DATA to it)
                    )
                }
            }
            setNbOnItemClickListener { adapter,view,position ->
                val baseMessage = data[position]
                if (baseMessage.sendingStatus != BaseMessage.SendingStatus.SUCCEEDED) return@setNbOnItemClickListener
                val fileMessage = baseMessage as? FileMessage ?: return@setNbOnItemClickListener
                val preView = this@ChatFrag.preViewUtils
                preView.preImage(fileMessage.url,preView.imageList.indexOfFirst { it.url == fileMessage.url })
            }
            setOnItemChildClickListener { adapter,view,position ->
                val baseMessage = data[position]
                when (view.id) {
                    R.id.message_status_group_chat -> {
                        if (isFailedMessage(baseMessage))
                            retryFailedMessage(baseMessage)
                    }
                }
            }
        }
    }

    private var mChannel: GroupChannel? = null

    /**
     * 对方用户
     */
    val mMember get() = mChannel?.members?.find { it.userId != mine?.resourceId }


    override fun layoutId(): Int = R.layout.frg_chat

    override fun initView(savedInstanceState: Bundle?) = with(mDatabind) {
        mDatabind.model = mViewModel
        mDatabind.click = Click()
        chatAdapter.load(channelUrl)
        recyclerView.init(chatAdapter,LinearLayoutManager(requireContext(),RecyclerView.VERTICAL,true).apply { stackFromEnd = true })
        tvNickname.text = arguments?.getString(EXTRA_GROUP_CHANNEL_NAME,"")
        if (accountIsDel) {
            layoutGroupChatChatbox.isVisible = false
            showDelUI()
        }
        edittextGroupChatMessage.addTextChangedListener {
            it ?: return@addTextChangedListener
            if (it.length > 1000) {
                it.delete(1000,it.length)
                mViewModel.toast.postValue("Maximum length is 1000 characters")
            }
        }
        toolbar.setNavigationOnClickListener {
            nav().navigateUp()
        }
    }

    override fun lazyLoadData() {
        ConnectionManager.addConnectionManagementHandler(mine?.resourceId) { _ ->
            refresh()
        }
    }

    override fun createObserver() {
        super.createObserver()
        observerMessage()
    }

    /**
     * 观察消息变化
     */
    private fun observerMessage() {
        eventViewModel.messageUpdatedEvent.observe(this) {
            val (channel,message) = it
            if (channel.url == channelUrl) {
                chatAdapter.update(message)
            }
        }
        eventViewModel.messageDelEvent.observe(this) {
            val (baseChannel,msgId) = it
            if (baseChannel.url == channelUrl) {
                chatAdapter.delete(msgId)
            }
        }
        eventViewModel.messageReceivedEvent.observe(this) {
            val (baseChannel,baseMessage) = it
            if (baseChannel.url == channelUrl) {
                chatAdapter.markAllMessagesAsRead()
                chatAdapter.addFirst(baseMessage)
            }
        }
    }

    /**
     * 刷新消息列表
     */
    fun refresh() {
        if (mChannel == null) {
            ChannelManager.enterChannel(channelUrl) {
                mChannel = it ?: return@enterChannel finish()
                mViewModel.userId = mChannel?.members?.find { it.userId != mine?.resourceId }?.metaData?.get("userId") ?: ""
                chatAdapter.channel = mChannel
                chatAdapter.loadLatestMessages(CHANNEL_LIST_LIMIT) { list,e ->
                    chatAdapter.markAllMessagesAsRead()
                }
                showErrorUI()
            }
        } else {
            mChannel?.refresh {
                chatAdapter.loadLatestMessages(CHANNEL_LIST_LIMIT) { list,e ->
                    chatAdapter.markAllMessagesAsRead()
                }
            }
        }
    }

    /**
     * 拉黑 注销
     */
    private fun showErrorUI() {
        val member = mMember ?: return
        val hasHeaderLayout = chatAdapter.hasHeaderLayout()
        if (!hasHeaderLayout) {
            when {
                member.isBlockedByMe -> {
                    headerView.text = "You have been blacklisted."
                    chatAdapter.addHeaderView(headerView)
                }
                member.isBlockingMe -> {
                    headerView.text = "You have been blacklisted."
                    chatAdapter.addHeaderView(headerView)
                }
            }
        } else {
            if (member.isBlockingMe.not() || member.isBlockedByMe.not())
                chatAdapter.removeAllHeaderView()
        }
    }

    private fun showDelUI() {
        val hasHeaderLayout = chatAdapter.hasHeaderLayout()
        if (!hasHeaderLayout) {
            if (accountIsDel) {
                headerView.text = "The other party's account has been cancelled"
                chatAdapter.addHeaderView(headerView)
            }
        }
    }

    private fun finish() {
        nav().navigateUp()
    }

    override fun onDestroyView() {
        chatAdapter.save()
        super.onDestroyView()
        ConnectionManager.removeConnectionManagementHandler(mine?.resourceId)
    }


    private val pictureDialog: PictureDialog
        get() = PictureDialog(this,1) { list ->
            val localMedia = list.firstOrNull() ?: return@PictureDialog
            val isImage = localMedia.mimeType.contains("image")
            if (isImage) sendFileWithThumbnail(localMedia)
            else processorVideo(localMedia)
        }


    private fun processorVideo(localMedia: LocalMedia) {
        showLoading()
        lifecycleScope.launch(Dispatchers.IO) {
            var fos:FileOutputStream? = null
            requireContext().cacheDir.runCatching {
                if (exists().not()) {
                    mkdir()
                }
                val fileName = System.currentTimeMillis().toString() + ".jpg"
                val file: File = File(this,fileName)
                val mediaMetadataRetriever = MediaMetadataRetriever()
                mediaMetadataRetriever.setDataSource(localMedia.realPath)
                fos = FileOutputStream(file)
                mediaMetadataRetriever.getFrameAtTime(1000L*1000L, MediaMetadataRetriever.OPTION_NEXT_SYNC)?.compress(Bitmap.CompressFormat.JPEG,100,fos)
                fos?.flush()
                fos?.close()
                file
            }.onSuccess {
                withContext(Dispatchers.Main) {
                    val uploadFile = mViewModel.uploadFile(it)
                    localMedia.compressPath = uploadFile?.url
                    sendFileWithThumbnail(localMedia)
                }
            }.onFailure {
                fos?.close()
            }
            withContext(Dispatchers.Main) {
                dismissLoading()
            }
        }
    }

    private fun sendFileWithThumbnail(local: LocalMedia) {
        mChannel ?: return
        val path = local.realPath
        val file = File(path)
        val mime = local.mimeType
        val size = local.size.toInt()
        if (path == null || path == "") {
            Toast.makeText(activity,"File must be located in local storage.",Toast.LENGTH_LONG).show()
        } else {
            val fileMessageHandler = SendFileMessageHandler { fileMessage,e ->
                if (e != null) {
                    chatAdapter.markMessageFailed(fileMessage)
                    return@SendFileMessageHandler
                }
                chatAdapter.markMessageSent(fileMessage)
            }
            // Send image with thumbnails in the specified dimensions
            val tempFileMessage = mChannel!!.sendFileMessage(file,local.fileName,mime,size,local.compressPath,null,null,fileMessageHandler)
            chatAdapter.addTempFileMessageInfo(tempFileMessage,local)
            chatAdapter.addFirst(tempFileMessage)
            mDatabind.recyclerView.scrollToPosition(0)
        }
    }

    private fun retryFailedMessage(message: BaseMessage) {
        if (message is UserMessage) {
            sendUserMessage(message.message)
        } else if (message is FileMessage) {
            val uri: LocalMedia = chatAdapter.getTempFileMessageUri(message) ?: return
            sendFileWithThumbnail(uri)
        }
        chatAdapter.removeFailedMessage(message)
    }

    fun sendUserMessage(text: String) {
        if (text.trim().isEmpty()) return
        val groupChannel = mChannel ?: return
        val tempUserMessage: UserMessage = groupChannel.sendUserMessage(text,SendUserMessageHandler { userMessage,e ->
            if (e != null) {
                chatAdapter.markMessageFailed(userMessage)
                return@SendUserMessageHandler
            }
            chatAdapter.markMessageSent(userMessage)
        })
        // Display a user message to RecyclerView
        chatAdapter.addFirst(tempUserMessage)
        mViewModel.sendText.set("")
        mDatabind.recyclerView.scrollToPosition(0)
    }

    inner class Click {
        private val moreList
            get() = arrayListOf(
                BtnItems(getString(R.string.report),R.mipmap.icon_report),
                BtnItems(
                    appContext.getString(if (mMember?.isBlockedByMe == true) R.string.un_block_user else R.string.block_user),
                    R.mipmap.icon_black,
                    0xfff95862.toInt()
                )
            )


        fun sendUserMessage() {
            sendUserMessage(mViewModel.sendText.get())
        }

        fun more() {
            hideSoftKeyboard(requireActivity())
            if (accountIsDel) {
                mViewModel.run { toast.postValue("The other party's account has been cancelled") }
                return
            }
            BottomSheetListDialog(requireContext(),moreList) { position ->
                when (position) {
                    0 -> {
                        ReportUserDialogFragment.crate(mViewModel).show(parentFragmentManager,"report")
                    }
                    1 -> {
                        if (mMember?.isBlockedByMe == true) {
                            SendBird.unblockUser(mMember) { _ ->
                                showErrorUI()
                            }
                        } else
                            SendBird.blockUser(mMember,UserBlockHandler { user,e ->
                                showErrorUI()
                            })

                    }
                }
            }.show(childFragmentManager,"chat")
        }

        fun selectFile() {
            hideSoftKeyboard(requireActivity())
            if (accountIsDel) {
                mViewModel.run { toast.postValue("The other party's account has been cancelled") }
                return
            }
            pictureDialog.show(childFragmentManager,"picture")
        }
    }
}